package cn.hebutrun.oulabot.quartz;

import cn.hebutrun.oulabot.common.CustomException;
import cn.hebutrun.oulabot.common.ErrorEnum;
import cn.hebutrun.oulabot.common.util.LogUtil;
import cn.hebutrun.oulabot.common.util.TimeUtil;
import cn.hebutrun.oulabot.entity.response.QuartzDetailResponse;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;

/**
 * @author zhaolong
 * @since 2022-04-02
 */
@Component
@RequiredArgsConstructor
public class QuartzUtil {

  private final Scheduler defaultScheduler;

  public JobBuilder initJobBuilder(
      Class<? extends Job> clazz, TriggerKey triggerKey, JobDataMap param) {
    return this.initJobBuilder(clazz, triggerKey, param, "");
  }

  public JobBuilder initJobBuilder(
      Class<? extends Job> clazz, TriggerKey triggerKey, JobDataMap param, String description) {
    JobBuilder jobBuilder =
        JobBuilder.newJob(clazz).withIdentity(triggerKey.getName(), triggerKey.getGroup());
    if (param != null && !param.isEmpty()) {
      jobBuilder.usingJobData(param);
    }
    if (description != null && !description.isBlank()) {
      jobBuilder.withDescription(description);
    }
    return jobBuilder;
  }

  /**
   * 增加定时任务 根据日期
   *
   * @param date 工作执行时间
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param param 额外的定义参数
   */
  public void addQuartzJobByDate(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      Date date,
      JobDataMap param,
      String description)
      throws SchedulerException {
    this.addQuartzJobByDate(clazz, triggerKey, date, param, description, false);
  }

  /**
   * 增加定时任务 根据日期
   *
   * @param date 工作执行时间
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param param 额外的定义参数
   * @param cover 是否覆盖
   * @since 2022/10/9
   * @author zhaolong
   */
  public void addQuartzJobByDate(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      Date date,
      JobDataMap param,
      String description,
      Boolean cover)
      throws SchedulerException {
    this.checkExist(triggerKey, cover);

    TriggerBuilder<SimpleTrigger> triggerBuilder =
        TriggerBuilder.newTrigger()
            .withIdentity(TriggerKey.triggerKey(triggerKey.getName(), triggerKey.getGroup()))
            .startAt(date)
            .withSchedule(
                SimpleScheduleBuilder.simpleSchedule().withRepeatCount(0).withIntervalInSeconds(0));

    this.start(
        triggerKey, this.initJobBuilder(clazz, triggerKey, param, description), triggerBuilder);
  }

  /**
   * 增加定时任务 根据日期
   *
   * @param date 工作执行时间
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   */
  public void addQuartzJobByDate(
      Class<? extends Job> clazz, TriggerKey triggerKey, Date date, String description)
      throws SchedulerException {
    this.addQuartzJobByDate(clazz, triggerKey, date, null, description);
  }

  public void addQuartzJobByDate(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      LocalDateTime localDateTime,
      JobDataMap param,
      String description)
      throws SchedulerException {
    this.addQuartzJobByDate(clazz, triggerKey, TimeUtil.toDate(localDateTime), param, description);
  }

  public void addQuartzJobByDate(Class<? extends Job> clazz, TriggerKey triggerKey, Date date)
      throws SchedulerException {
    this.addQuartzJobByDate(clazz, triggerKey, date, null, "");
  }

  public void addQuartzJobByDate(
      Class<? extends Job> clazz, TriggerKey triggerKey, LocalDateTime localDateTime)
      throws SchedulerException {
    this.addQuartzJobByDate(clazz, triggerKey, localDateTime, "");
  }

  public void addQuartzJobByDate(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      LocalDateTime localDateTime,
      String description)
      throws SchedulerException {
    this.addQuartzJobByDate(clazz, triggerKey, TimeUtil.toDate(localDateTime), null, description);
  }

  private void checkExist(TriggerKey triggerKey, Boolean cover) throws SchedulerException {
    if (defaultScheduler.getTrigger(triggerKey) != null) {
      if (cover) {
        LogUtil.warn(
            StrUtil.format("定时任务已存在 {} - {} 覆盖执行", triggerKey.getGroup(), triggerKey.getName()));
        this.deleteJob(triggerKey);
      } else {
        LogUtil.quartzStartError(triggerKey, ErrorEnum.QUARTZ_ALREADY_EXIST.getMsg());
        throw new CustomException(ErrorEnum.QUARTZ_ALREADY_EXIST);
      }
    }
  }

  private void start(TriggerKey triggerKey, JobBuilder jobBuilder, TriggerBuilder<?> triggerBuilder)
      throws SchedulerException {
    this.start(triggerKey, jobBuilder.build(), triggerBuilder);
  }

  private void start(TriggerKey triggerKey, JobDetail jobDetail, TriggerBuilder<?> triggerBuilder)
      throws SchedulerException {
    triggerBuilder.forJob(jobDetail).withIdentity(triggerKey);
    defaultScheduler.scheduleJob(jobDetail, triggerBuilder.build());

    if (!defaultScheduler.isShutdown()) {
      defaultScheduler.start();
      LogUtil.quartzStartInfo(jobDetail);
    } else {
      LogUtil.warn("defaultScheduler已关闭");
    }
  }

  /**
   * 增加定时任务
   *
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param triggerBuilder 具体执行时间
   * @param param 额外的定义参数
   */
  public void addQuartzJob(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      TriggerBuilder<?> triggerBuilder,
      JobDataMap param)
      throws SchedulerException {
    this.addQuartzJob(clazz, triggerKey, triggerBuilder, param, false);
  }

  /**
   * 增加定时任务
   *
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param triggerBuilder 具体执行时间
   * @param param 额外的定义参数
   * @param cover 是否覆盖
   */
  public void addQuartzJob(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      TriggerBuilder<?> triggerBuilder,
      JobDataMap param,
      Boolean cover)
      throws SchedulerException {
    this.checkExist(triggerKey, cover);

    this.start(triggerKey, this.initJobBuilder(clazz, triggerKey, param), triggerBuilder);
  }

  /**
   * 增加定时任务
   *
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param triggerBuilder 具体执行时间
   */
  public void addQuartzJob(
      Class<? extends Job> clazz, TriggerKey triggerKey, TriggerBuilder<?> triggerBuilder)
      throws SchedulerException {
    this.addQuartzJob(clazz, triggerKey, triggerBuilder, null);
  }

  /**
   * 添加定时任务
   *
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param cron 执行表达式
   * @param param 额外的定义参数
   * @param cover 是否覆盖原有的
   */
  public void addQuartzJob(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      String cron,
      JobDataMap param,
      String description,
      Boolean cover)
      throws SchedulerException {
    this.checkExist(triggerKey, cover);

    TriggerBuilder<CronTrigger> triggerBuilder =
        TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(cron));

    this.start(
        triggerKey, this.initJobBuilder(clazz, triggerKey, param, description), triggerBuilder);
  }

  /**
   * 添加定时任务
   *
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param triggerBuilder 具体执行时间
   * @param param 额外的定义参数
   * @param description 任务描述
   * @param cover 是否覆盖原有的
   */
  public void addQuartzJob(
      Class<? extends Job> clazz,
      TriggerKey triggerKey,
      TriggerBuilder<?> triggerBuilder,
      JobDataMap param,
      String description,
      Boolean cover)
      throws SchedulerException {
    this.checkExist(triggerKey, cover);

    this.start(
        triggerKey, this.initJobBuilder(clazz, triggerKey, param, description), triggerBuilder);
  }

  /**
   * 添加定时任务
   *
   * @param clazz 工作类 需继承Job
   * @param triggerKey 工作组、工作名
   * @param cron 执行表达式
   */
  public void addQuartzJob(Class<? extends Job> clazz, TriggerKey triggerKey, String cron)
      throws SchedulerException {
    this.addQuartzJob(clazz, triggerKey, cron, null, "", false);
  }

  public void addQuartzJob(
      Class<? extends Job> clazz, TriggerKey triggerKey, String cron, String description)
      throws SchedulerException {
    this.addQuartzJob(clazz, triggerKey, cron, null, description, false);
  }

  public boolean modifyJobTime(TriggerKey triggerKey, TriggerBuilder<?> triggerBuilder)
      throws SchedulerException {
    return defaultScheduler.rescheduleJob(triggerKey, triggerBuilder.build()) != null;
  }

  public void deleteJob(TriggerKey triggerKey) throws SchedulerException {
    this.deleteJob(new JobKey(triggerKey.getName(), triggerKey.getGroup()));
  }

  public void deleteJob(JobKey jobKey) throws SchedulerException {
    if (!defaultScheduler.deleteJob(jobKey)) {
      LogUtil.quartzDeleteError(jobKey, ErrorEnum.QUARTZ_NOT_FOUND.getMsg());
      throw new CustomException(ErrorEnum.QUARTZ_NOT_FOUND);
    }
  }

  public void deleteAllJob() throws SchedulerException {
    LogUtil.quartzDeleteInfo("删除所有任务");
    if (!defaultScheduler.deleteJobs(
        new ArrayList<>(defaultScheduler.getJobKeys(GroupMatcher.anyJobGroup())))) {
      LogUtil.error("删除所有定时任务失败");
      throw new CustomException(ErrorEnum.QUARTZ_DEL_FAIL);
    }
  }

  public List<QuartzDetailResponse> getJobDetailList() throws SchedulerException {
    return this.getJobDetailList(defaultScheduler.getJobKeys(GroupMatcher.anyGroup()));
  }

  public List<QuartzDetailResponse> getJobDetailList(String group, String name)
      throws SchedulerException {
    return this.getJobDetailList(defaultScheduler.getJobKeys(GroupMatcher.groupContains(group)))
        .stream()
        .filter(x -> x.getName().contains(name))
        .toList();
  }

  private List<QuartzDetailResponse> getJobDetailList(Set<JobKey> jobKeys) {
    return jobKeys.stream()
        .map(
            x -> {
              QuartzDetailResponse res = null;
              try {
                JobDetail jobDetail = defaultScheduler.getJobDetail(x);
                res =
                    new QuartzDetailResponse()
                        .setGroup(x.getGroup())
                        .setName(x.getName())
                        .setDescription(jobDetail.getDescription())
                        .setParam(jobDetail.getJobDataMap())
                        .setJobClassName(jobDetail.getJobClass().getSimpleName());
              } catch (SchedulerException e) {
                LogUtil.error(
                    ErrorEnum.QUARTZ_NOT_FOUND.getMsg() + " " + x.getGroup() + " " + x.getName());
              }
              return res;
            })
        .filter(Objects::nonNull)
        .toList();
  }

  public List<QuartzDetailResponse> getJobDetailListLikeGroup(String group)
      throws SchedulerException {
    return this.getJobDetailList(defaultScheduler.getJobKeys(GroupMatcher.groupContains(group)));
  }

  public List<QuartzDetailResponse> getJobDetailListLikeName(String name)
      throws SchedulerException {
    return this.getJobDetailList(defaultScheduler.getJobKeys(GroupMatcher.anyGroup())).stream()
        .filter(x -> x.getName().contains(name))
        .toList();
  }
}
